package com.lagou.edu.filter;

import com.lagou.edu.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * 恶意IP过滤器
 * @author
 */
@Slf4j
@Component
@RefreshScope
public class MaliciousIpFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(MaliciousIpFilter.class);
    /**
     * key : IP地址
     * value : 访问的时间集合
     */
    private static Map<String, List<Long>> accessRecord = new ConcurrentHashMap<>();

    @Value("${access.limit.time}")
    private int time;

    @Value("${access.limit.frequency}")
    private int frequency;

    @Reference
    private UserService userService;

    private static final Pattern LOGIN_URL_PATTERN = Pattern.compile("/api/user/login/*");

    private static final Pattern REGISTER_URL_PATTERN = Pattern.compile("/api/user/register/*");

    private static final Pattern USER_PATTERN = Pattern.compile("/api/user/*");

    /**
     * 过滤器的核心方法
     * @param exchange 封装了request 和 response 对象的上下文
     * @param chain 网关过滤器链（包括全局过滤器路由和单路由过滤器）
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        String clientIp = request.getRemoteAddress().getHostString();

        //统一认证
        Boolean isAccess = UnifiedCertification(request, response);
        if(!isAccess){
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            log.debug("=====>IP:" + clientIp + " 认证未通过");
            String data = "请先进行登录操作！";
            DataBuffer wrap = response.bufferFactory().wrap(data.getBytes(Charset.forName("UTF-8")));
            return response.writeWith(Mono.just(wrap));
        }

        Integer times = countIpAccessTimes(clientIp);
        if(times > frequency){
            response.setStatusCode(HttpStatus.SEE_OTHER);
            log.debug("=====>IP:" + clientIp + " 访问过于频繁，拒绝访问请求!");
            String data = "您频繁进⾏操作，请求已被拒绝";
            DataBuffer wrap = response.bufferFactory().wrap(data.getBytes(Charset.forName("UTF-8")));
            return response.writeWith(Mono.just(wrap));
        }

        //通过校验，记录访问时间
        long currentTimeMillis = System.currentTimeMillis();
        List<Long> timeMillisList = accessRecord.get(clientIp);
        if(timeMillisList == null){
            synchronized (this){
                timeMillisList = accessRecord.get(clientIp);
                if(timeMillisList == null){
                    timeMillisList = new CopyOnWriteArrayList<>();
                    timeMillisList.add(currentTimeMillis);
                    accessRecord.put(clientIp, timeMillisList);
                } else {
                    timeMillisList.add(currentTimeMillis);
                }
            }
        } else {
            timeMillisList.add(currentTimeMillis);
        }

        return chain.filter(exchange);
    }

    /**
     * 统计在限定时间的内的访问次数
     * @param ip
     * @return
     */
    private Integer countIpAccessTimes(String ip){
        int count = 0;
        List<Long> timeMillisList = accessRecord.get(ip);
        if(timeMillisList == null || timeMillisList.size() == 0){
            return count;
        }
        long currentTimeMillis = System.currentTimeMillis();
        long beginTime = currentTimeMillis - time * 1000;
        for (Long time : timeMillisList) {
            if(time > beginTime){
                count ++;
            }
        }
        return count;
    }

    private Boolean UnifiedCertification(ServerHttpRequest request, ServerHttpResponse response){
        String url = request.getURI().getPath();
        Matcher matcher = USER_PATTERN.matcher(url);
        if(matcher.find()){
            Matcher loginMatcher = LOGIN_URL_PATTERN.matcher(url);
            Matcher registerMatcher = REGISTER_URL_PATTERN.matcher(url);
            if(loginMatcher.find() || registerMatcher.find()){
                String[] split = url.split("\\/");
                if(split.length == 6){
                    String email = split[4];
                    String password = split[5];
                    String login = userService.login(email, password);
                    if(StringUtils.isEmpty(login)){
                        return false;
                    }
                    String token = userService.createToken(email, password);
                    ResponseCookie responseCookie = ResponseCookie.from("token", token)
                            .path("/api/user/")
                            .sameSite("Lax").build();
                    response.addCookie(responseCookie);
                    return true;
                }
            } else {
                MultiValueMap<String, HttpCookie> cookies = request.getCookies();
                if(cookies.size() == 0){
                    return false;
                }
                String token = cookies.getFirst("token").getValue();
                String info = userService.info(token);
                log.info("====> email is : " + info);
                if(StringUtils.isEmpty(info)){
                    return false;
                }
                return true;
            }
        }
        return true;
    }

    /**
     * 数值越小，优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}
